package org.nextprot.api.core.aop;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.nextprot.api.commons.utils.KeyValueRepresentation;
import org.nextprot.api.commons.utils.StringUtils;
import org.nextprot.api.core.aop.requests.RequestInfo;
import org.nextprot.api.core.aop.requests.RequestInfoFactory;
import org.nextprot.api.core.aop.requests.RequestManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* Aspect responsible to instrument the actions of the users
*
* @author Daniel Teixeira
* @version $Revision$, $Date$, $Author$
*/
@Aspect
@ManagedResource(objectName = "org.nextprot.api:name=ControllersInstrumentation", description = "My Managed Bean", log = true, logFile = "jmx.log", persistPeriod = 200, persistLocation = "/tmp", persistName = "bar")
public class InstrumentationAspect {
@Autowired
private RequestManager clientRequestManager;
private static final Log LOGGER = LogFactory.getLog(InstrumentationAspect.class);
private boolean enableInstrumentation = true;
private AtomicLong daoIdCounter = new AtomicLong();
private AtomicLong controllerRequestIdCounter = new AtomicLong();
private ThreadLocal<Long> controllerRequestId = new ThreadLocal<Long>();
private AtomicLong serviceRequestIdCounter = new AtomicLong();
private ThreadLocal<Long> serviceRequestId = new ThreadLocal<Long>();
@Around("execution(* org.nextprot.api.*.controller.*.*(..))")
public Object logServiceInformaton(ProceedingJoinPoint pjp) throws Throwable {
if (enableInstrumentation) {
controllerRequestId.set(controllerRequestIdCounter.incrementAndGet());
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Annotation[][] annotations = methodSignature.getMethod().getParameterAnnotations();
Object[] arguments = pjp.getArgs();
RequestInfo request = RequestInfoFactory.createRequestInfo(methodSignature.getDeclaringType().getSimpleName() + "#" + methodSignature.getName());
request.putAll(extractSecurityInfo());
request.putAll(extractHttpInfo());
clientRequestManager.startMonitoringClientRequest(request);
StringBuilder sb = new StringBuilder();
sb.append("type=Controller;");
addMethodParameters(sb, methodSignature);
addArgumentsParameters(sb, arguments, annotations);
sb.append("controllerRequestId=");
sb.append(controllerRequestIdCounter.get());
sb.append(";");
for (String key : request.keySet()) {
sb.append(key);
sb.append("=");
sb.append(request.get(key));
sb.append(";");
}
long start = System.currentTimeMillis();
// Proceed to method invocation
try {
//LOGGER.info("aspect=before;" + sb);
Object result = pjp.proceed();
addTimeElapsed(sb, System.currentTimeMillis() - start);
addResultParameters(sb, result);
LOGGER.info("aspect=after;" + sb);
clientRequestManager.stopMonitoringCurrentRequestInfo();
controllerRequestId.remove();
return result;
} catch (Exception e) {
addTimeElapsed(sb, System.currentTimeMillis() - start);
addExceptionParameters(sb, e);
LOGGER.info("aspect=after;" + sb);
clientRequestManager.stopMonitoringCurrentRequestInfo(e);
controllerRequestId.remove();
throw e;
}
} else {
return pjp.proceed();
}
}
@Around("execution(* org.nextprot.api.*.service.*.*(..))")
public Object logService(ProceedingJoinPoint pjp) throws Throwable {
if (enableInstrumentation) {
serviceRequestId.set(serviceRequestIdCounter.incrementAndGet());
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Annotation[][] annotations = methodSignature.getMethod().getParameterAnnotations();
Object[] arguments = pjp.getArgs();
StringBuilder sb = new StringBuilder();
sb.append("type=Service;");
addMethodParameters(sb, methodSignature);
addArgumentsParameters(sb, arguments, annotations);
sb.append("serviceRequestId=");
sb.append(serviceRequestIdCounter.get());
sb.append(";");
Long cId = serviceRequestId.get();
if (cId != null) {
sb.append("controllerRequestId=");
sb.append(cId);
sb.append(";");
}
long start = System.currentTimeMillis();
try {
Object result = pjp.proceed();
//LOGGER.info("aspect=before;" + sb);
addTimeElapsed(sb, System.currentTimeMillis() - start);
addResultParameters(sb, result);
serviceRequestId.remove();
LOGGER.info("aspect=after;" + sb);
return result;
} catch (Exception e) {
addTimeElapsed(sb, System.currentTimeMillis() - start);
addExceptionParameters(sb, e);
serviceRequestId.remove();
LOGGER.info("aspect=after;" + sb);
throw e;
}
} else {
return pjp.proceed();
}
}
@Around("execution(* org.nextprot.api.*.dao.*.*(..))")
public Object logDao(ProceedingJoinPoint pjp) throws Throwable {
if (enableInstrumentation) {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Annotation[][] annotations = methodSignature.getMethod().getParameterAnnotations();
Object[] arguments = pjp.getArgs();
StringBuilder sb = new StringBuilder();
sb.append("type=DAO;");
addMethodParameters(sb, methodSignature);
addArgumentsParameters(sb, arguments, annotations);
sb.append("DAOId=");
sb.append(daoIdCounter.incrementAndGet());
sb.append(";");
Long sId = serviceRequestId.get();
if (sId != null) {
sb.append("serviceRequestId=");
sb.append(sId);
sb.append(";");
}
Long cId = controllerRequestId.get();
if (cId != null) {
sb.append("controllerRequestId=");
sb.append(cId);
sb.append(";");
}
long start = System.currentTimeMillis();
try {
//LOGGER.info("aspect=before;" + sb);
Object result = pjp.proceed();
addTimeElapsed(sb, System.currentTimeMillis() - start);
addResultParameters(sb, result);
LOGGER.info("aspect=after;" + sb);
return result;
} catch (Exception e) {
addTimeElapsed(sb, System.currentTimeMillis() - start);
addExceptionParameters(sb, e);
LOGGER.info("aspect=after;" + sb);
throw e;
}
} else {
return pjp.proceed();
}
}
private static StringBuilder addArgumentsParameters(StringBuilder sb, Object[] arguments, Annotation[][] annotations) {
for (int i = 0; i < arguments.length; i++) {
Annotation[] annots = annotations[i];
Value v = null;
for (Annotation a : annots) {
if (a.annotationType().equals(Value.class)) {
v = (Value) a;
break;
}
}
if (v == null) {
sb.append("arg" + (i + 1));
} else {
sb.append(v.value());
}
sb.append("=");
Object argument = arguments[i];
if (argument == null) {
sb.append("null");
} else if (argument instanceof KeyValueRepresentation) {
sb.append(((KeyValueRepresentation)argument).toKeyValueString());
} else if (argument instanceof Collection) {
sb.append(((Collection<?>) argument).size());
} else if (argument instanceof String || argument instanceof Long || argument instanceof Short || argument instanceof Integer) {
sb.append(argument);
} else {
sb.append("representation-unknown");
}
sb.append(";");
}
return sb;
}
private static StringBuilder addTimeElapsed(StringBuilder sb, long time) {
sb.append("timeElapsed=");
sb.append(time);
sb.append(";");
return sb;
}
private static StringBuilder addMethodParameters(StringBuilder sb, MethodSignature methodSignature) {
sb.append("class=");
sb.append(methodSignature.getDeclaringType().getSimpleName());
sb.append(";");
sb.append("method=");
sb.append(methodSignature.getName());
sb.append(";");
sb.append("timeStart=");
sb.append(System.currentTimeMillis());
sb.append(";");
return sb;
}
private static StringBuilder addResultParameters(StringBuilder sb, Object result) {
sb.append("timeEnd=");
sb.append(System.currentTimeMillis());
sb.append(";");
if (result instanceof Collection) {
sb.append("resultSize=");
sb.append(Collection.class.cast(result).size());
sb.append(";");
}else if (result instanceof KeyValueRepresentation) {
sb.append(KeyValueRepresentation.class.cast(result).toKeyValueString());
}
return sb;
}
private static StringBuilder addExceptionParameters(StringBuilder sb, Exception e) {
sb.append("timeEnd=");
sb.append(System.currentTimeMillis());
sb.append(";");
sb.append("exception=");
sb.append(e.getClass().getName());
sb.append(";");
if (e.getMessage() != null){
sb.append("exceptionMsg=");
sb.append(e.getMessage().trim().replaceAll("=", "").replaceAll(";", "="));
sb.append(";");
}
return sb;
}
private static Map<String, String> extractHttpInfo(){
HashMap<String, String> map = new HashMap<String, String>();
HttpServletRequest httpRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
map.put("http-request-x-forwarded-for", httpRequest.getHeader("x-forwarded-for"));
map.put("http-request-x-forwarded-host", httpRequest.getHeader("x-forwarded-host"));
map.put("http-request-x-forwarded-server", httpRequest.getHeader("x-forwarded-server"));
map.put("http-request-origin", httpRequest.getHeader("origin"));
map.put("http-request-accept", StringUtils.quote(httpRequest.getHeader("accept")));
map.put("http-request-content-type", StringUtils.quote(httpRequest.getHeader("content-type")));
map.put("http-request-referer", httpRequest.getHeader("referer"));
//map.put("http-request-user-agent", StringUtils.quote(httpRequest.getHeader("user-agent")));
//map.put("http-request-content-type", httpRequest.getContentType());
map.put("http-request-method", StringUtils.quote(httpRequest.getMethod()));
map.put("http-request-context-path", StringUtils.quote(httpRequest.getContextPath()));
map.put("http-request-query-string", StringUtils.quote(httpRequest.getQueryString()));
map.put("http-request-uri", StringUtils.quote(httpRequest.getRequestURI()));
map.put("http-request-url", StringUtils.quote(httpRequest.getRequestURL().toString()));
map.put("http-request-remote-host", StringUtils.quote(httpRequest.getRemoteHost()));
map.put("http-request-remote-user", StringUtils.quote(httpRequest.getRemoteUser()));
map.put("http-request-local-name", StringUtils.quote(httpRequest.getLocalName()));
map.put("http-request-local-address", StringUtils.quote(httpRequest.getLocalAddr()));
map.put("http-request-path-info", StringUtils.quote(httpRequest.getPathInfo()));
Enumeration<String> params = httpRequest.getParameterNames();
while (params.hasMoreElements()) {
String paramName = params.nextElement();
String paramValue = httpRequest.getParameter(paramName);
map.put("http-request-param-" + paramName, StringUtils.quote(paramValue));
}
return map;
}
private static Map<String, String> extractSecurityInfo() {
Authentication a = SecurityContextHolder.getContext().getAuthentication();
HashMap<String, String> map = new HashMap<>();
if (a != null) {
if (a.getDetails() instanceof Map) {
Map<?,?> userDetails = (Map<?,?>) a.getDetails();
for(Object o : userDetails.keySet()){
map.put("auth0" + StringUtils.capitalizeFirstLetter(o.toString()), StringUtils.quote(userDetails.get(o).toString()));
}
}
if (a.getPrincipal() instanceof UserDetails) {
UserDetails currentUserDetails = (UserDetails) a.getPrincipal();
map.put("securityUserName", currentUserDetails.getUsername());
map.put("securityUserRole", StringUtils.quote(currentUserDetails.getAuthorities().toString()));
} else {
map.put("securityUserName", a.getPrincipal().toString());
}
} else {
map.put("securityUserName", "unknown");
}
return map;
}
@ManagedAttribute
public boolean getInstrumentationEnabled() {
return enableInstrumentation;
}
@ManagedAttribute
public void setInstrumentationEnabled(boolean enableInstrumentation) {
this.enableInstrumentation = enableInstrumentation;
}
}